package com.security.securityAutoConfiguration;

import com.security.authority.metadata.AbstractSecurityMetadataSource;
import com.security.authority.voter.AuthorityPatternVoter;
import com.security.filter.AbstractTokenAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.AbstractAccessDecisionManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
import org.springframework.security.config.annotation.web.configurers.UrlAuthorizationConfigurer;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import java.util.List;

import static com.security.authority.metadata.AbstractSecurityMetadataSource.SECURITY_METADATA_SOURCE_BEAN_NAME;
import static com.security.filter.AbstractTokenAuthenticationFilter.TOKEN_AUTH_FILTER_BEAN_NAME;

/**
 * @author 大忽悠
 * @create 2022/10/7 18:34
 */
@Configuration
@ConditionalOnClass({WebSecurityConfigurerAdapter.class})
@RequiredArgsConstructor
@Slf4j
public class SecurityAutoConfiguration extends WebSecurityConfigurerAdapter {
    private final String[] ARRAY_TYPE = new String[]{};
    /**
     * 需要放行的swagger相关请求
     */
    private final List<String> SWAGGER_REQUEST_IGNORE = List.of("/css/**", "/fonts/**", "/images/**", "/js/**", "/webjars/**",
            "/doc.html#/**", "/swagger-resources", "/v3/**", "/doc.html", "/swagger-ui.html");

    @Autowired(required = false)
    @Qualifier(TOKEN_AUTH_FILTER_BEAN_NAME)
    private AbstractTokenAuthenticationFilter tokenAuthenticationFilter;
    @Autowired(required = false)
    @Qualifier(SECURITY_METADATA_SOURCE_BEAN_NAME)
    private AbstractSecurityMetadataSource securityMetadataSource;

    @Override
    public void configure(WebSecurity web) throws Exception {
        log.info("开启springSecurity对swagger相关资源的权限放行");
        web.ignoring().antMatchers(SWAGGER_REQUEST_IGNORE.toArray(ARRAY_TYPE));
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.formLogin().disable();
        http.httpBasic().disable();
        addTokenAuthFilter(http);
        http.removeConfigurer(LogoutConfigurer.class);
        dynamicAuthorityCheck(http);
    }

    private void dynamicAuthorityCheck(HttpSecurity http) throws Exception {
        if(securityMetadataSource==null){
            return;
        }
        log.info("springSecurity开启了动态权限检查");
        ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class);
        http.apply(new UrlAuthorizationConfigurer<>(applicationContext))
                //配置SecurityMetadataSource---控制获取当前URL访问权限的方式
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setSecurityMetadataSource(securityMetadataSource);
                        //如果访问当前资源不需要任何权限,那么直接放行
                        object.setRejectPublicInvocations(false);
                        return object;
                    }
                })
                //配置AccessDecisionManager---添加自定义的投票器
                .withObjectPostProcessor(new ObjectPostProcessor<AccessDecisionManager>() {
                    @Override
                    public <O extends AccessDecisionManager> O postProcess(O object) {
                        AbstractAccessDecisionManager decisionManager = (AbstractAccessDecisionManager) object;
                        List<AccessDecisionVoter<?>> decisionVoters = decisionManager.getDecisionVoters();
                        //默认只添加我们自定义的投票器,默认添加的投票器移除
                        decisionVoters.clear();
                        decisionVoters.add(new AuthorityPatternVoter());
                        return object;
                    }
                });
    }

    private void addTokenAuthFilter(HttpSecurity http) {
        if(tokenAuthenticationFilter==null){
            return;
        }
        log.info("在springSecurity的过滤器链中加入了tokenAuthenticationFilter");
        http.addFilterBefore(tokenAuthenticationFilter, LogoutFilter.class);
    }
}
